<?php

	class st_model extends CI_Model {
		
		const RESET_FILTER			  = true;
		const DO_NOT_RESET_FILTER	  = false;
		const LOAD_ALL_ATTRIBUTE      = true;
		const LOAD_SELECTED_ATTRIBUTE = false;
		
		/**Include Table Name And Private Key Name Of Model*/
		protected $_instance  = array(
										  'table'      => null, 
										  'primaryKey' => null,
									  );
		/**
		* attributes_selected: Attribute To Selected Will Store In Array
		* selected_all_attribute: Load All Attribute Of Model Nor Not
		*/
		private $_config = array(
							'select_all_attribute' => false,
						);
		/**All Dynamic Attribute Of Model*/
		private $_data        = array();
		
		/**Check For An Entity Loaded*/
		private $_loaded      = false;
		/**
		* Attributes = array('attribute_name' => stdClass(name, type, length, ...));
		* Alias      = array('attribute_name' => array('alias1', 'alias2'));
		* Limit      = array('lowerBound' = number, 'upperBound' => number)
		* Orders     = array('attribute_name' => 'DESC', 'attribute_name_2' => 'ASC',...)
		* Groups     = array('attribute_name1' => attribute_name1, ...);
		* Having     = array('condition1', 'condition2')
		* BlackList  = array(1, 3, 4, ...) , ID Of Entity Model
		*/
		private $_spell = array(
					'attributes_selected'  => array(),
					'attributes_filtered'  => array(),	
					'alias'      => array(),
					'limit'      => array('lowerBound' => false, 'upperBound' => false),
					'orders'     => array(),
					'groups'     => array(),
					'having'     => array(),
					'whitelist'  => array(),
					'blacklist'  => array()
				);
		/**Current SQL Query Stored*/
		private $_query = '';
		/**Count collection items*/
		private $_total = 0;

		public function __construct($_instance = array()) {
			parent::__construct();
			$this->load->database();
			if (!is_array($_instance) || count($_instance) < 2) {
				return $this;
			}
			if ($_pk = $_instance['primaryKey'] && isset($_pk)) {
				$this->setPrimaryKey($_pk);
			}
			$this->load->config();
			$_prefix = $this->config->item('dbprefix');
			if ($_tb = $_instance['table'] && isset($_tb)) {
				$this->__setTable($_prefix . $_tb);
			}
			$this->__initQuery();
		}
		
		public function newInstance() 
		{
			$className = get_class($this);
			return new $className();
		}
		
		public function __initQuery() {
			if ($this->__isValid()) {
				$this->_query = sprintf('SELECT %s FROM %s', $this->getPrimaryKey(), $this->__getTable());
				return $this;
			}
			return false;
		}
		
		/**
		* Make execute query
		*/
		public function query($_query)
		{
			if (!$_query) {
				return null;
			}
			$result = $this->db->query($_query);
			return $result;
		}
		
		/**
		* get value row query
		*/
		public function getQueryValue($query, $field)
		{
			$_value = null;
			$_res = $this->db->query($query);
			if ($_res->num_rows() > 0) {
				$_value = $_res->row()->$field;
			}
			return $_value;
		}

		/**
		* Get Primary Key Name
		* @return string name of primary key
		*/
		public function getPrimaryKey() {
			return (string)$this->_instance['primaryKey'];
		}
		
		/**
		* Set Primary Key Name
		* @param string primary key name
		* @return success: st_model, failed: false
		*/
		public function setPrimaryKey($_pk = '') {
			if (is_string($_pk) && !empty($_pk)) {
				$this->_instance['primaryKey'] = (string)$_pk;
				return $this;
			}
			return false;
			
		}
		
		/**
		* Get Table Name
		* @return string name of table instance
		*/
		protected function __getTable() {
			return (string)$this->_instance['table'];
		}
		
		public function table()
		{
			return $this->__getTable();
		}
		
		/**
		* Set Table Name
		* @param string tablename
		* @return success: st_model, failed: false
		*/
		protected function __setTable($_tb = '') {
			if (is_string($_tb) && !empty($_tb)) {
				$this->_instance['table'] = (string)$_tb;
				return $this;
			}
			return false;
		}
		
		/**
		* Get Or Set Attribute Of Model
		* Ex:
		* field in table: meta_keywords
		* get: getMetaKeywords();
		* set: setMetaKeywords($_value);
		*/
		public function __call($method, $args) {
			$_method_type = substr($method, 0, 3);
			$_regex       = '/([A-Z]{1,1}[^A-Z.]*)/';
			$_replace     = '$1_';
			$_property    = strtolower(preg_replace($_regex, $_replace, substr($method, 3)));
			$_property    = substr($_property, 0, strlen($_property) - 1);
			if (count($this->_spell['alias']) > 0) {
				foreach ($this->_spell['alias'] as $_attribute => $_alias) {
					if (in_array($_property, $_alias)) {
						$_property = $_attribute;
						break;
					}
				}	
			}
			switch ($_method_type) {
				case 'get':
					if (!isset($_property) || !$this->isLoaded()) {
						return false;
					}
					if (isset($this->_data[$_property])) {
						return $this->_data[$_property];	 
					}
					return false;
					break;
				case 'set':
					if (isset($args[0])) {
						$this->_data[$_property] = $args[0];
					}
					return $this;
					break;
				default:
					return false;
					break;
			}
		}

		/**
		* Check Model is valid, valid when model has instance table name and primary key name
		* @return valid: true, invalid: false
		*/
		private function __isValid() {
			if (!isset($this->_instance['primaryKey']) || !isset($this->_instance['table'])) {
				return false;
			}
			return true;
		}
		
		/**
		* Get Config Data Of Model
		* @return array config data of model
		*/
		public function getConfigData() {
			return (array)$this->_config;	
		}
		
		/**
		* Set Config Data Of Model
		* @param array config data
		* @return success: st_model, failured: false
		*/
		public function setConfigData($_config = array()) {
			if (!is_array($_config)) {
				return false;	
			}
			$this->_config = $_config;
			return $this;
		}
		
		/**
		* Get Config Of Model
		* @param string config key
		* @return success: config value, failured: throw Exception
		*/
		public function getConfig($_conf_key) {
			if (empty($_conf_key) || !is_string($_conf_key))	{
				throw new Exception('Config Key Must Be NOT NULL');	
			}
			return $this->_config[$_conf_key];
		}
		
		/**
		* Set Config Of Model
		* @param string config key
		* @param string config value
		* @return success: st_model, failured: false
		*/
		public function setConfig($_conf_key, $_conf_value) {
			if (empty($_conf_key) || !is_string($_conf_key)) {
				return false;	
			}
			$this->_config[$_conf_key] = $_conf_value;
			return $this;
		}
		
		/**
		* Reset Config Of Model
		* @return st_model		
		*/
		public function resetConfig() {
			$this->_config = array();	
			return $this;
		}
		
		/**
		* Load Entity By Value Of Primary Key
		* @param string, integer $_pkval: value of primary key
		* @param boolean load all attribute or load selected attributes
		* @return success: st_model, failured: false
		*/
		public function loadByPK($_pkVal = '', $_loadAllAttribute = true) {
			if (!$this->__isValid()) {
				return false;
			}
			// Reset Loaded
			$this->resetLoad();
			$_sql = "SELECT * FROM %s WHERE %s = %s";
			$_tb  = $this->__getTable();
			$_pk  = $this->getPrimaryKey();
			$_sql = sprintf($_sql, $_tb, $_pk, $_pkVal);
			$_res = $this->db->query($_sql);
			if ($_res->num_rows() > 0) {
				// Start spec method type is setData, ex: setDescription, setName, etc...
				$method = 'set';
				$fields = array();
				// If Select All Attribute of Model
				$this->setConfig('select_all_attribute', $_loadAllAttribute);
				// If Select All Attribute
				if ($this->getConfig('select_all_attribute')) {
					$fields   = $this->db->list_fields($_tb);
				} else {
					// If Count Of Elements In Selected Attribute Equal 0
					// then Add Primary Key Names To List
					if (count($this->_spell['attributes_selected']) == 0 || !isset($this->_spell['attributes_selected'][$this->getPrimaryKey()])) {
						$this->_spell['attributes_selected'][$this->getPrimaryKey()] = TRUE;	
					} 
					// iterator attribute selected list then add to fields
					// Filter Attribute Disabled
					$fields = array_keys(array_filter($this->_spell['attributes_selected']));
				}
				// Set Data To List
				foreach ($fields as $field) {
					// Concat property name and 'set' string to set method data: setName...
					$property  = implode('', array_map('ucfirst', explode('_', $field)));
					$method   .= $property;
					// Set Data
					$this->$method($_res->row()->$field);
					// Reset method type name again
					$method    = 'set';
				}
				// Loaded successfull, set current query
				$this->setCurrentQuery($_sql);
				$this->_loaded = true;
			}
			// Free Memory
			$_res->free_result();
			unset($_sql, $_tb, $_pk, $_loadAllAttribute);
			return $this;
		}
		
		/**
		* Load Model By Query
		* @param string query
		* @param string spec primary key name of query
		* @return model
		*/
		public function loadByQuery($_query = '', $_pk = 'default', $_loadAllAttribute = true) {
			if (!$this->__isValid()) {
				return false;
			}
			if (!is_string($_query) || empty($_query)) {
				return false;	
			}
			if (empty($_pk) || $_pk == 'default') {
				$_pk = $this->getPrimaryKey();
			}
			// Reset Model loaded
			$this->resetLoad();
			// Execute Query
			$_res = $this->db->query($_query);
			if ($_res->num_rows() > 0) {
				$this->loadByPK($_res->row()->$_pk, $_loadAllAttribute);
				if (!$this->isLoaded()) {
					return false;	
				}
				$this->setCurrentQuery($_query);
				$_res->free_result();
				unset($_query, $_res);
			}
			return $this;
		}
		
		/**
		* Load By Attribute Filter Orther LoadByPK
		* @param string query
		* @param string spec primary key name of query
		* @return model
		*/
		public function loadByFilter($_loadAllAttribute = true) {
			if (!$this->__isValid()) {
				return false;
			}
			// Reset Model Loaded
			$this->resetLoad();
			if ($_loadAllAttribute) {
				$this->setConfig('select_all_attribute', true);	
			}
			$_select_query    = $this->__analyzeSelectQuery();
			$_from_query      = $this->__analyzeFromQuery();
			$_condition_query = $this->__analyzeConditionQuery();
			$_pkName          = $this->getPrimaryKey();
			$_query           = "$_select_query $_from_query $_condition_query";
			$_res             = $this->db->query($_query);
			// If Not Found Any Row By Attribute Filter
			if ($_res->num_rows() == 0) {
				return false;
			}
			// If Found
			$this->loadByPK($_res->row()->$_pkName);
			if (!$this->isLoaded()) {
				return false;	
			}
			$this->setCurrentQuery($_query);
			$_res->free_result();
			unset($_query, $_select_query, $_from_query, $_condition_query, $_pkName, $_res);
			return $this;
		}
		
		/**
		* Load By stdClass
		* @param stdClass
		* @return model
		*/
		public function loadByStdClass($_std = null) {
			if (!is_object($_std) || !isset($_std->primaryKey) || !isset($_std->tableName)) {
				return false;	
			}
			$this->__setTable($_std->tableName);
			$this->setPrimaryKey($_std->primaryKey);
			if (isset($_std->id)) {
				$this->_data[$_std->primaryKey] = (string)$_std->id;
				$this->_loaded = true; 
			}
			return $this;
		}
		
		/**
		* Update All Data Of Model Into Database
		* If Model Not Loaded, Then Update Data Of It In Update Mode, else In Create New Mode
		* @return st_model
		*/
		public function save() {
			if ($this->isLoaded()) {
				// Update Data In Row
				$_sql = 'UPDATE ' . $this->__getTable() . ' SET ';
				foreach ($this->_data as $_key => $_value) {
					if ($_key != $this->getPrimaryKey()) {
						$_sql .= '`' . $_key . '`' . '="' . addslashes($_value) . '", ';
					}
				}
				$_sql = substr($_sql, 0, strlen($_sql) - 2);
				$_sql .= ' WHERE ' . $this->getPrimaryKey() . '=' . $this->getId();
			} else {
				// Create New Row
				$_sql  = 'INSERT INTO ' . $this->__getTable();
				$_sql .= ' (' . implode(', ', array_keys($this->_data)) . ')';
				$_sql .= ' VALUES (' . implode(', ', array_map('add_quote', array_values($this->_data))) . ')';
				// Find Last Insert ID Model
				$_lastQuery = 'select '.$this->getPrimaryKey().' as id from '.$this->table() . ' order by '.$this->getPrimaryKey().' desc limit 1';
				$_res = $this->db->query($_lastQuery);
				if ($_res->num_rows() > 0) {
					$_pk = $this->getPrimaryKey();
					$id = intval($_res->row()->id) + 1;
					$this->setData($_pk, $id);
					// Confirm Loaded Data
					$this->_loaded = true;
				}
				$_res->free_result();
			}
			$this->db->query($_sql);
			$this->setCurrentQuery($_sql);
			unset($_sql);
			return $this;
		}
	
		/**
		* Delete An Row
		* @return success: st_model, failured: false
		*/
		public function delete() {
			if (!$this->isLoaded()) {
				return false;
			}
			$_sql = 'DELETE FROM ' . $this->__getTable() .
				   ' WHERE ' . $this->getPrimaryKey() . '=' . $this->getId();
			$this->db->query($_sql);
			$this->setCurrentQuery($_sql);
			unset($_sql);
			return $this;
		}
		
		/**
		* Check Model Is Loaded
		* @return boolean
		*/
		public function isLoaded() {
			return $this->_loaded;
		}
		
		/**
		* Count Original Collection Of Model
		* @return integer
		*/
		public function count() {
			$_sql = "SELECT ".$this->getPrimaryKey()." AS id FROM ".$this->__getTable();
			$_res = $this->db->query($_sql);
			return $_res->num_rows();
		}
	
		/**
		* Format datetime value of attribute by format string datetime
		* @param string format datetime string
		* @param string attribute to be format
		* @return string datetime string by format
		*/
		public function formatDate($formatStr = '', $propName) {
			if (!$this->isLoaded() || !isset($this->_data[$propName])) {
				return null;
			}
			return date_format(date_create($this->getData($propName)), $formatStr);
		}
		
		/**
		* Serialize Model If Required To Save It In Database Or Session
		* @return string
		*/
		public function serialize() {
			return serialize(__CLASS__);	
		}
		
		/**
		* UnSerialize packet string to model class
		* @return st_model
		*/
		public function unserialize($_packet) {
			if (!isset($_packet))
				return unserialize($_packet); 
			return false;
		}
		
		/**
		* Get Current Query Executed
		* @return string 
		*/
		public function getCurrentQuery() {
			return (string)$this->_query;	
		}
		
		/**
		* Set currrent Query
		* @param string query
		* @return success: st_model, failured: false
		*/
		public function setCurrentQuery($query = '') {
			if (empty($query) || !is_string($query)) {
				return false;	
			}	
			$this->_query = $query;
			return $this;
		}
		
		/** handler collection data*/
		/**
		* Get Spell
		*/
		protected function __getMagicSpell() {
			return (array)$this->_spell;
		}
		
		/** handler collection data*/
		/**
		* Check Exists Attribute
		* @param string $_attribute_name: name of attribute selected to filter%value%
		*/
		public function __existsAttribute($_attribute_name, $_checkAgain = true) {
			if ($_checkAgain) {
				if (empty($_attribute_name) || !is_string($_attribute_name)) {
					return false;
				}
			}
			return $this->db->field_exists($_attribute_name, $this->__getTable());
		}
		
		/** handler collection data*/
		/**
		* Get Field Meta Data
		* @param string $_field_name: name of attribute selected to filter%value%
		*/
		public function getFieldMetaData($_field_name) {
			if (!$this->__existsAttribute($_field_name)) {
				return null;	
			}
			$_fields = $this->db->field_data($this->__getTable());
			foreach ($_fields as $_field) {
			   if ($_field->name == $_field_name) {
				   return $_field;
			   }
			}
			return null;
		}
		
		/**
		* Add Attribute Name To Select
		* @param string | array attributes to select
		* @param boolean is funtion to call or not
		* @return success: st_model, failured: false
		*/
		public function addAttributeToSelect($_attribute, $_callableMode = true) {
			if (!empty($_attribute) && is_string($_attribute)) {
				if ($_attribute == '*') {
					$this->setConfig('select_all_attribute', true);
					return $this;
				}
				// If Not Function Ex: Count, Avg, Sum... Then Check Exists Attribute
				if (!$_callableMode) {
					$_field = $this->getFieldMetaData($_attribute);
					// Check Exists Filed
					if ($_field === null) {
						return false;
					}
				}
				$this->_spell['attributes_selected'][$_attribute] = true;
			} elseif (is_array($_attribute)) {
				foreach ($_attribute as $_attr) {
					$this->addAttributeToSelect($_attr, $_callableMode);
				}	
			}
			return $this;
		}
		
		/**
		* Remove Attribute From Selected List
		* @param string attribute to select
		* @return success: st_model, failured: false
		*/
		public function removeAttributeFromSelectedList($_attribute) {
			if (empty($_attribute) || !is_string($_attribute) || !isset($this->_spell['attributes_selected'][$_attribute])) {
				return false;	
			}
			unset($this->_spell['attributes_selected'][$_attribute]);
			return $this;
		}
		
		/**
		* Enable Attribute From Selected List
		* @param string attribute to select
		* @return success: st_model, failured: false
		*/
		public function enableAttributeSelected($_attribute) {
			if (!is_string($_attribute) || empty($_attribute)) {
				return false;
			}
			$this->_spell['attributes_selected'][$_attribute] = true;
			return $this;
		}
		
		/**
		* Disable Attribute From Selected List
		* @param string attribute to select
		* @return success: st_model, failured: false
		*/
		public function disableAttributeSelected($_attribute) {
			if (empty($_attribute) || !is_string($_attribute) || !isset($this->_spell['attributes_selected'][$_attribute])) {
				return false;	
			}
			$this->_spell['attributes_selected'][$_attribute] = false;
			return $this;
		}
		
		/**
		* Reset All Attribute To Select
		* @return st_model
		*/
		public function resetSelectedList() {
			$this->_spell['attributes_selected'] = array();
			return $this;
		}
		
		/**
		* Get Selected List
		* @return array
		*/
		public function getSelectedList() {
			return (array)$this->_spell['attribute_selected'];	
		}
		
		/**
		* Add Alias For Attribute
		* @param string attribute name
		* @param string | array alias for attribute
		* @return success: st_model, failured: false
		*/
		public function addAliasForAttribute($_attribute, $_alias) {
			// Check Empty Attribute Name And Attribute Name must be a string
			$_field = $this->getFieldMetaData($_attribute);
			if ($_field === null) {
				return false;
			}
			if (!isset($this->_spell['alias'][$_attribute])) {
				$this->_spell['alias'][$_attribute] = array();
			}
			if (is_string($_alias)) {
				$this->_spell['alias'][$_attribute][] = $_alias;
			} elseif (is_array($_alias)){
				foreach($_alias as $_as) {
					$this->addAliasForAttribute($_attribute, $_as);
				}	
			}
			return $this;
		}
		
		/**
		* Reset All Alias For Attribute
		* @param string attribute name
		* @return success: st_model, failured: false
		*/
		public function resetAliasOfAttribute($_attribute) {
			// Check Empty Attribute Name And Attribute Name must be a string
			$_field = $this->getFieldMetaData($_attribute);
			if ($_field === null || !isset($this->_spell['alias'][$_attribute])) {
				return false;
			}
			unset($this->_spell['alias'][$_attribute]);
			return $this;
		}
		
		/**
		* Reset All Alias
		* @return st_model
		*/
		public function resetAlias() {
			$this->_spell['alias'] = array();
			return $this;
		}
		
		/**
		* Add Attribute Value To Filter Collection Later
		* @param string $_attribute_name: name of attribute selected to filter
		* @param mixed $_value: any value but object
		* @param boolean $_use_literal: use compare string in literal mode mysql or not, ex: like %value%
		*/
		public function addAttributeToFilter($_attribute, $_value, $_op = '=', $_logical = 'AND') {
			// Check Empty Attribute Name And Attribute Name must be a string
			$_field = $this->getFieldMetaData($_attribute);
			if ($_field === null) {
				return false;
			}
			//Set Value to Filter 
			$_field->value    = $_value;
			// Set Operator Of Field To Value
			$_field->operator = $_op;
			// Logical
			$_field->logical  = strtoupper($_logical);
			$this->_spell['attributes_filtered'][$_value . '_' . $_op . '_' . $_logical] = (array)$_field;
			return $this;
		}
		
		/**
		* Reset Filter By Attribute Name
		* @param string $_attribute_name: name of attribute selected to filter%value%
		*/
		public function resetAttributeFilter($_attribute = '') {
			$_field = $this->getFieldMetaData($_attribute);
			if ($_field === null || !isset($this->_spell['attributes'][$_field->name])) {
				return false;
			}
			unset($this->_spell['attributes_filtered'][$_field->name]);
			return $this;
		}
		
		/**
		* Reset All Filter
		* @param string $_attribute_name: name of attribute selected to filter%value%
		*/
		public function resetFilteredList() {
			$this->_spell['attributes_filtered'] = array();
			return $this;
		}
		
		/**
		* Get Filtered List
		* @return array
		*/
		public function getFilteredList() {
			return (array)$this->_spell['attribute_filtered'];	
		}
		
		/**
		* Handler Set Limit For Collection
		*/
		public function setLowerBound($_lowerBound) {
			$this->_spell['limit']['lowerBound'] = intval($_lowerBound);
			return $this;
		}
		
		public function setUpperBound($_upperBound) {
			$this->_spell['limit']['upperBound'] = intval($_upperBound);
			return $this;
		}
		
		public function getLowerBound() {
			if (!is_integer($this->_spell['limit']['lowerBound'])) {
				return false;
			}
			return $this->_spell['limit']['lowerBound'];
		}
		
		public function getUpperBound() {
			if (!is_integer($this->_spell['limit']['upperBound'])) {
				return false;
			}
			return $this->_spell['limit']['upperBound'];
		}
		
		public function setLimit($_lowerBound = '', $_upperBound = '') {
			$this->setLowerBound($_lowerBound);
			$this->setUpperBound($_upperBound);
			return $this;
		}
		
		public function getLimit() {
			return (array)$this->_spell['limit'];	
		}
		
		/**
		* Reset Upper Limit
		* @param string $_attribute_name: name of attribute selected to filter%value%
		*/
		public function resetUpperLimit() {
			$this->_spell['limit']['upperBound']= false;
		}
		
		/**
		* Reset Limit
		* @param string $_attribute_name: name of attribute selected to filter%value%
		*/
		public function resetLimit() {
			$this->_spell['limit'] = array('lowerBound' => false, 'upperBound' => false);
			return $this;
		}
		
		/**
		* End Handler
		*/
		
		/**
		* Add Order by Attribute
		* @param string $_attribute_name: name of attribute
		* @param string $_order_type: Order of Attribute (asc | desc)
		* @return mixed st_model if success, false if failed
		*/
		public function addAttributeOrder($_attribute, $_order_type = 'desc') {
			if (!$this->__existsAttribute($_attribute)) {
				return false;	
			}
			$_spell = & $this->_spell;
			$_order_type = strtolower($_order_type);
			switch ($_order_type) {
				case 'asc':
				case 'desc':
					$_spell['orders'][$_attribute] = $_order_type;
					break;
				default:
					return false;
					break;
			}			
			return $this;
		}
		
		/**
		* Reset Order Of Attribute
		* @param string $_attribute_name: name of attribute
		* @return mixed st_model if success, false if failed
		*/
		public function resetAttributeOrder($_attribute) {
			if (empty($_attribute) || !is_string($_attribute) || !isset($this->_spell['orders'][$_attribute])) {
				return false;
			}	
			unset($this->_spell['orders'][$_attribute]);
			return $this;
		}
		
		/**
		* Reset Order Of Attribute
		* @return mixed st_model
		*/
		public function resetOrderedList() {
			$this->_spell['orders'] = array();
			return $this;
		}
		
		/**
		* Add Id To BlackList, remove ID from whitelist if exist
		* @param string $_attribute_value: id of attribute
		* @return mixed st_model if success, false if failed
		*/
		public function addIdToBlackList($_id = '') {
			if(empty($_id)){
				return false;
			}
			// Remove Id From White if exists
			if (in_array($_id, $this->_spell['whitelist'])) {
				unset($this->_spell['whitelist']);
			}
			$this->_spell['blacklist'][$_id] = $_id;
			return $this;
		}
		
		/**
		* Remove Id From BlackList
		*@param id
		*return mixel st_model if success, false if failed
		*/
		public function removeIdFromBlaskList($_id = '') {
			if(empty($_id)){
				return false;
			}
			unset($this->_spell['blacklist'][$_id]);
			return $this;
		}
		
		/**
		* Reset BlackList		
		*/
		public function resetBlackList() {
			$this->_spell['blacklist'] = array();
			return $this;
		}
		
		/**
		* Get BlackList		
		*/
		public function getBlackList() {
			return (array)$this->_spell['blacklist'];
		}
		
		/**
		* Set BlackList		
		*/
		public function setBlackList($_blacklist = array()) {
			// if set blacklist must be greater than 0 elements
			if (!is_array($_blacklist) || count($_blacklist) == 0) {
				return false;
			}
			$this->_spell['blacklist'] = $_blacklist;
			return $this;
		}
		
		
		/** Add Id To WhiteList
		* If ID existed in blacklist then remove from blacklist
		* @param string $_attribute_value: id of attribute
		* @return mixed st_model if success, false if failed
		*/
		public function addIdToWhiteList($_id = '') {
			if(empty($_id)){
				return false;
			}
			// Remove Id From BackList if exists
			if (in_array($_id, $this->_spell['blacklist'])) {
				unset($this->_spell['blacklist']);
			}
			$this->_spell['whitelist'][$_id] = $_id;
			return $this;
		}
		
		/**
		*Remove Id From WhiteList
		*@param string id
		*return mixel st_model if success, false if failed
		*/
		public function removeIdFromWhiteList($_id = '') {
			if(empty($_id)){
				return false;
			}
			unset($this->_spell['whitelist'][$_id]);
			return $this;
		}
		
		/**
		* Reset WhiteList		
		*/
		public function resetWhiteList() {
			$this->_spell['whitelist'] = array();
			return $this;
		}
		
		/**
		* Get BlackList		
		*/
		public function getWhiteList() {
			return (array)$this->_spell['whitelist'];
		}
		
		/**
		* Set BlackList		
		*/
		public function setWhiteList($_whitelist = array()) {
			// if set whitelist must be greater than 0 elements
			if (!is_array($_whitelist) || count($_whitelist) == 0) {
				return false;
			}
			$this->_spell['whitelist'] = $_whitelist;
			return $this;
		}
		
		/**
		* Set Orther Spell To Spell Of Model
		* @param array spell to set
		* @return success: st_model, failured: throw exception
		*/
		public function learningSpell($_spell = array()) {
			$this->__checkValidSpell($_spell);
			$this->_spell = $_spell;
			return $this;	
		}
		
		/**
		* If Set New Spell then check validation of it
		* @param array spell to check
		* @return success: st_model, failured: throw exception
		*/
		protected function __checkValidSpell($_spell = array()) {
			$_elements = array(
							   'attributes_selected', 
							   'attributes_filtered', 
							   'alias', 
							   'limit', 
							   'orders', 
							   'groups', 
							   'having', 
							   'blacklist'
						);
			foreach ($_elements as $_element) {
				if (!array_key_exists($_element, $_spell)) {
					throw new Exception('Spell Array Must Be Has Elements: ' . implode(', ', $_elements));
				}
			}
			return $this;
		}
		
		/**
		* Reset Spell
		* @return st_model
		*/
		public function resetSpell() {
			$this->resetSelectedList();
			$this->resetFilteredList();
			$this->resetOrderedList();
			$this->resetWhiteList();
			$this->resetBlackList();
			$this->resetGroupBy();
			$this->resetHaving();
			$this->resetLimit();	
			return $this;
		}
		
		/**
		* Reset Data
		* @return st_model
		*/
		public function resetData() {
			$this->_data = array();
			return $this;
		}
		
		/**
		* Reset Loaded
		* @return st_model
		*/
		public function resetLoad() {
			$this->_loaded = false;
			return $this;
		}
		
		/**
		* Reset All	
		* @return st_model
		*/
		public function reset() {
			$this->resetConfig();
			$this->resetSpell();
			$this->resetData();
			$this->resetLoad();
			return $this;
		}
		
		/**
		* Add Attribute To Group By
		* @param string attribute name
		* @return success: st_model, failured: false
		*/
		public function addAttributeToGroup($_attribute = '') {
			if(empty($_attribute)){
				return false;
			}
			$this->_spell['groups'][$_attribute] = true;
			return $this;
		}
		
		/**
		* Remove Attribute Group By
		* @param string attribute name
		* @return success: st_model, failured: false
		*/
		public function removeAttributeToGroup($_attribute = '') {
			if (empty($_attribute) || !isset($this->_spell['groups'][$_attribute])) {
				return false;	
			}
			unset($this->_spell['groups'][$_attribute]);
			return $this;
		}
		
		/**
		* Reset Group By
		* @return st_model
		*/
		public function resetGroupBy() {
			$this->_spell['groups'] = array();
			return $this;	
		}
		
		/**
		* Add Condition For Having Group By
		* @param string condition key
		* @param string condition
		* @return success: st_model, failured: false
		*/
		public function addHavingCondition($_key, $_condition) {
			if (empty($key)) {
				return false;	
			}
			$this->_spell['having'][$_key] = $_condition;
			return $this;
		}
		
		/**
		* Remove Condition By Key
		* @param string condition key
		* @return success: st_model, failured: false
		*/
		public function removeHavingCondition($_key) {
			if (empty($_key) || !isset($this->_spell['having'][$_key])) {
				return false;	
			}
			unset($this->_spell['having'][$_key]);
			return $this;	
		}
		
		/**
		* Reset Condition Having
		* @return st_model
		*/
		public function resetHaving() {
			$this->_spell['having'] = array();
			return $this;	
		}
		
		/**
		* Analyzed All Spell elements To Load All Entity By Condition and etc...
		* Step1:
		*	Select All Or Several Attribute
		*   Set Alias For Attributes
		*   Set Condition For Selection
		*   Set Order By Attribute
		*   Set Limit Of Collection
		*/
		protected function __analyzeSpellToQuery($_return = false) {
			
			// Handler Select Query
			$_select_query    = $this->__analyzeSelectQuery();
			// Handler From Query
			$_from_query      = $this->__analyzeFromQuery();			
			// Handler Condition Query
			$_condition_query = $this->__analyzeConditionQuery();
			// Handler White List
			$_in_query        = $this->__analyzeWhiteListQuery();
			// Handler Black List
			$_not_in_query    = $this->__analyzeBlackListQuery();
			// Handler Order Query
			$_order_query     = $this->__analyzeOrderQuery();
			// Group By
			$_group_by_query  = $this->__analyzeGroupByQuery();
			// Handler Limit Query
			$_limit_query     = $this->__analyzeLimitQuery();
			// Binding The Query
			$_query = "$_select_query $_from_query $_condition_query $_in_query $_not_in_query $_order_query $_group_by_query $_limit_query";
			// Set Current Query
			$this->setCurrentQuery($_query);
			unset($_select_query, $_from_query, $_condition_query, $_in_query, $_not_in_query, $_order_query, $_group_by_query, $_limit_query);
			// return query or not
			if ($_return) {
				return $_query;
			}
			return $this;
			
		}
		
		/**
		* Analyze Select Query
		* @return string select query
		*/
		protected function __analyzeSelectQuery() {
			$_select_query = 'SELECT ';
			// Handler Select Query
			if ($this->getConfig('select_all_attribute') === true) {
				$_select_query    = 'SELECT * ';	
			} else {
				if (count($this->_spell['attributes_selected']) == 0 || !isset($this->_spell['attributes_selected'][$this->getPrimaryKey()])) {
					$this->_spell['attributes_selected'][$this->getPrimaryKey()] = TRUE;	
				} 
				$_selected = array_keys(array_filter($this->_spell['attributes_selected']));
				$_selected = implode(', ', $_selected);
				$_select_query .= $_selected;
			}
			return $_select_query;
		}
		
		/**
		* Analyze From Query
		* @return string from table name
		*/
		protected function __analyzeFromQuery() {
			return ' FROM ' . $this->__getTable();	
		}
		
		/**
		* Analyze Condition For Selection
		* @return string condition query
		*/
		protected function __analyzeConditionQuery() {
			$_condition_query = '';
			// Handler Condition Query
			$_filtered_list = $this->_spell['attributes_filtered'];
			if (count($_filtered_list) > 0) {
				foreach ($_filtered_list as $_attribute) {
					switch (strtolower($_attribute['type'])) {
						case 'string':
							$_like_ops = array('=', '==', 'LIKE', 'like', 'IS', 'is', 'EQUAL', 'equal', 'EQUALS', 'equals');
							// Default Operator Is "Like"
							$_op = 'NOT LIKE';
							// If Spec Operator Of Attribute Is Not Like Statement
							if (in_array($_attribute['operator'], $_like_ops)) {
								$_op = 'LIKE';
							} 
							$_condition_query .= $_attribute['logical'] . ' ' . $_attribute['name'] . ' ' . $_op  . ' "' . $_attribute['value'] . '"'; 
							break;
						default:
							$_condition_query .= ' ' . $_attribute['logical'] . ' ' . $_attribute['name'] . ' ' . $_attribute['operator'] . ' "' . $_attribute['value'] . '"';
							break;
					}	
				}
				//render Condition
				$_condition_query = 'WHERE ' . substr($_condition_query, 4);
			}
			return $_condition_query;
		}
		
		/**
		* Analyze Whitelist Query
		* @return string whitelist query
		*/
		protected function __analyzeWhiteListQuery() {
			$_in_query = '';
			//Handler White List
			$_white_list = $this->_spell['whitelist'];
			if (count($_white_list) > 0) {
				if (count($_filtered_list) == 0) {
					$_in_query = ' WHERE ' . $this->getPrimaryKey() . ' IN (%s)';
				} else {
					$_in_query = ' AND ' . $this->getPrimaryKey() . ' IN (%s)';
				}
				$_in_query = sprintf($_in_query, implode(',', $_white_list));
			}
			return $_in_query;
		}
		
		/**
		* Analyze Blacklist Query
		* @return string blacklist query
		*/
		protected function __analyzeBlackListQuery() {
			$_not_in_query = '';
			//Handler Black List
			$_black_list = $this->_spell['blacklist'];
			if (count($_black_list) > 0) {
				if (count($_filtered_list) == 0) {
					$_not_in_query = ' WHERE ' . $this->getPrimaryKey() . ' NOT IN (%s)';
				} else {
					$_not_in_query = ' AND ' . $this->getPrimaryKey() . ' NOT IN (%s)';
				}
				$_not_in_query = sprintf($_not_in_query, implode(',', $_black_list));
			}	
			return $_not_in_query;
		}
		
		/**
		* Analyze Order Query
		* @return string order query
		*/
		protected function __analyzeOrderQuery() {
			$_order_query = '';
			//Handler Order Query
			$_ordered_list = array_filter($this->_spell['orders']);
			if (count($_ordered_list) > 0) {
				$_order_query = 'ORDER BY ';
				foreach ($_ordered_list as $_attribute => $_order) {
					$_order_query .= "$_attribute $_order, ";
				}
				$_order_query = substr($_order_query, 0, strlen($_order_query) - 2);
			}	
			return $_order_query;
		}
		
		/**
		* Analyze Group Query
		* @return string group query
		*/
		protected function __analyzeGroupByQuery() {
			$_group_by_query = '';
			//Handler Group Query
			$_group_by_list = array_filter($this->_spell['groups']);
			if (count($_group_by_list) > 0) {
				$_group_by_query = 'GROUP BY ';
				foreach ($_group_by_list as $_attribute => $_enabled) {
					$_group_by_query .= "$_attribute, ";
				}
				$_group_by_query = substr($_group_by_query, 0, strlen($_group_by_query) - 2);
			}
			$_having_list = $this->_spell['having'];
			if (count($_having_list) > 0) {
				$_group_by_query .= ' HAVING ';
				foreach ($_having_list as $_condition) {
					$_group_by_query .= "$_condition, ";
				}
				$_group_by_query = substr($_group_by_query, 0, strlen($_group_by_query) - 2);
			}
			return $_group_by_query;	
		}
		
		/**
		* Analyze Limit Query
		* @return string limit query
		*/
		protected function __analyzeLimitQuery() {
			$_limit_query = '';
			//Handler Limit Query
			$_lowerBound  = $this->getLowerBound();
			$_upperBound  = $this->getUpperBound();
			if ($_lowerBound) {
				$_limit_query .= " LIMIT $_lowerBound";	
				if ($_upperBound) {
					$_limit_query .= ", $_upperBound";	
				}
			} elseif ($_upperBound) {
				$_limit_query .= " LIMIT $_upperBound";	
			}
			return $_limit_query;	
		}
		
		public function getEntities() {
			return $this->getCollection(false, false);
		}
		/**
		* Get Collection Of Model
		* @param boolean before get collection reset spell or not
		* @param boolean load all attribute or not, if not, it depend on config of model
		* @return array array of models
		*/
		public function getCollection($_resetSpell = false, $_loadAllAttribute = true) {
			if (!$_resetSpell) {
				$_query = $this->__analyzeSpellToQuery(true);
			} else {
				$_query = 'SELECT * FROM ' . $this->__getTable();
			}
			$_res = $this->db->query($_query);
			if ($_res->num_rows() > 0) {
				$_results    = array();
				$_primaryKey = $this->getPrimaryKey();
				foreach ($_res->result() as $_entity) {
					$_model = new $this;	
					$_model->setConfigData($this->getConfigData())
						   ->learningSpell($this->__getMagicSpell());  
					if ($_loadAllAttribute) {
						$_model->loadByPK($_entity->$_primaryKey, $_loadAllAttribute);
					} else {
						$_model->loadByPK($_entity->$_primaryKey, $_model->getConfig('select_all_attribute'));
					}
					$_results[$_model->getId()] = $_model;
				}
				$_res->free_result();
				unset($_query, $_res, $_primaryKey);
				return (count($_results) > 0) ? $_results : false;
			}
			return false;
		}
		
		/**
		* Get Collection By Query Filter And Etc...
		* @param string query
		* @param string spec primary key name of query
		* @return model
		*/
		public function getCollectionByQuery($_query = '', $_pkName = 'default') {
			if (!$this->__isValid()) {
				return false;
			}
			if (!is_string($_query) || empty($_query)) {
				return false;	
			}
			if (empty($_pkName) || $_pkName == 'default') {
				$_pkName = $this->getPrimaryKey();
			}
			// Execute Query
			$_res = $this->db->query($_query);
			// Get Class Name
			if ($_res->num_rows() > 0) {
				// Init Result Array
				$_results = array();
				foreach ($_res->result() as $_row) {
					$_model = new $this;
					$_model->loadByPK($_row->$_pkName, self::LOAD_ALL_ATTRIBUTE);
					$_results[$_model->getId()] = $_model; 
				}
				$this->setCurrentQuery($_query);
				$_res->free_result();
				unset($_query, $_res);
				if (count($_results) > 0) {
					$this->loadByPk(end($_results));
					return $_results;
				} else {
					return false;
				}
			}
			return $this;
		}
		
		/**
		* Get Attribute Extended By Format String: model_name/fk_name
		* @param string _attribute_path
		* @return model
		*/
		public function getAttributeExtended($_ext_path = '') {
			if (empty($_ext_path) || !is_string($_ext_path)) {
				return false;	
			}
			if (!$this->isLoaded()) {
				return false;	
			}
			$_attribute_model = explode('/', $_ext_path);
			if (count($_attribute_model) < 2) {
				throw new Exception('Attribute Extended Setup Need Format: model_attribute_name/linked_id, Ex: article_status/article_id');	
			}
			list($_attribute, $_fkName)  = $_attribute_model;
			if (!class_exists($_attribute)) {
				$this->load->model($_attribute);		
			}
			// Set Config And Spell For Attribute Model
			$_attribute = new $_attribute();
			$_attribute->addAttributeToFilter($_fkName, $this->getId(), '=')
					   ->loadByFilter();
			return $_attribute;
		}
		
		/**
		* Create model at runtime
		* @param string $_model_path: model_name/table_name/primarykey_name
		* @return model
		*/
		public static function createModel($_model_path = '') {
			if (!is_string($_model_path) || empty($_model_path)) {
				return false;
			}
			$_model = explode('/', $_model_path);
			if (count($_model) < 3) {
				throw new Exception('Model Create Runtime Need Format: model_name/table_name/primaryKey_name');	
			}
			list($_clsName, $_tbName, $_pkName)  = $_model;
			if (!class_exists($_clsName)) {
				$_renderModel = "class $_clsName extends st_model {" .
								" protected \$_instance = array(\"table\" => \"$_tbName\", \"primaryKey\" => \"$_pkName\"); };";
				eval($_renderModel);
			} 
			$_cls = new $_clsName();
			unset($_clsName, $_tbName, $_pkName, $_renderModel);
			return $_cls;
		}
		
		/**
		* Create model at runtime (Not Static)
		* @param string $_model_path: model_name/table_name/primarykey_name
		* @return model
		* Eg: product and category (category_product table)
		* insert new product (new row in product table)
		* insert new link category_product (new row in category_product table)
		*/
		public function createLink($path) {
			return self::createModel($path);
		}
		
		/**
		* Get Field Names Of Model Data
		* @return array
		*/
		public function getFieldNames()
		{
			if (!$this->isLoaded()) {
				return array();	
			}
			return array_keys($this->_data);	
		}
		
		/**
		* Get/Set Data
		* @return mixed
		*/
		public function setData($_key, $_value) {
			if (is_string($_key) && !empty($_key) && !empty($_value)) {
				$this->_data[$_key] = & $_value;	
			}
			return $this;
		}
		
		/**
		* If you want to set all data in array
		* Set All Data
		* @return mixed
		*/
		public function setEntireData($_data) {
			if (!is_array($_data)) {
				return $this;				
			}
			foreach ($_data as $key => $value) {
				$this->_data[$key] = $value;
			}
			return $this;
		}
		
		/**
		* Get/Set Data
		* @return mixed
		*/
		public function getData($_key) {
			if (isset($this->_data[$_key])) {
				return $this->_data[$_key];	
			}
			return null;
		}
		
		/**
		* Count All Items
		* @return mixed
		*/
		public function countAll()
		{
			if ($this->_total == 0) {
				$query = 'select count('.$this->getPrimaryKey().') as total from ' . $this->table() . ' limit 1';
				$_res = $this->db->query($query);
				if ($_res->num_rows() > 0) {
					$this->_total = $_res->row()->total;
				}
				$_res->free_result();
			}
			return $this->_total;
		}
		
		/*Count Item In Collection When Model Has More Conditions*/
		public function countTotal()
		{
			$_total = 0;
			$conditions = $this->__analyzeConditionQuery();
			if (strlen($conditions) > 0)
			{
				$query = 'select count('.$this->getPrimaryKey().') as total from ' . $this->table() . ' ' .$conditions  . ' limit 1';
				$_res = $this->db->query($query);
				if ($_res->num_rows() > 0) {
					$_total = $_res->row()->total;
				}
				$_res->free_result();
			} else {
				$_total = $this->countAll(); 
			}
			return $_total;
		}
		
		/*Update Value Of Field*/
		public function updateField($field, $value)
		{
			if (!$this->isLoaded())
			{
				return $this;
			}
			$query = 'update '.$this->table().' set '.$field.' = "'.$value.'" where '.$this->getPrimaryKey().'=' . $this->getId();
			$this->query($query);
			$this->setData($field, $value);
			return $this;
		}
	}	
?>
<?php 
	function add_quote($value) {
		if (strstr($value, 'function:')) {
			$value = str_replace('function:', '', $value);
			return $value;
		}
		return '"'.addslashes($value).'"';
	}
?>